package com.bobo.cretrofit;

import android.util.ArrayMap;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.ObservableSource;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.functions.Action;
import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.rxjava3.functions.Function;
import io.reactivex.rxjava3.schedulers.Schedulers;
import okhttp3.Call;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import retrofit2.Converter;
import retrofit2.HttpException;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory;

/**
 * 自定义ApiHelper（使用单例模式）
 * 多个基本路径时,要先添加基本路径到字典中{@link #addBaseUrl(String, String)}
 * Created by liubo on 10/1/16.
 */
public final class ApiHelper {
    private static final String TAG = "ApiHelper";
    private static final MediaType MEDIA_TYPE_STREAM = MediaType.parse("application/octet-stream");
    private IApiHandler mApiHandler;

    private ApiHelper() {
    }

    public static ApiHelper getInstance() {
        return ApiHelperHolder.INSTANCE;
    }

    private static class ApiHelperHolder {
        private static final ApiHelper INSTANCE = new ApiHelper();
    }

    /**
     * 默认基础路径
     */
    private String mDefBaseUrl = "/";

    private Retrofit mRetrofit;
    /**
     * 多ApiService管理
     * key:APi接口类的全路径
     * value:Api接口类的缓存实例对象
     * <p>
     * concurrentModificationException是为了防止在多线程场景下容器使用出现错误,这里使用ConcurrentHashMap避免该异常
     */
    private Map<String, Object> mApiServiceMap = new ConcurrentHashMap<>(1);
    /**
     * 多个基本路径管理
     */
    private ArrayMap<Integer, String> mBaseUrlMap = new ArrayMap<>();

    /**
     * 初始化基本配置
     *
     * @param baseUrl
     * @param handler
     */
    public void init(String baseUrl, IApiHandler handler) {
        this.mDefBaseUrl = baseUrl;
        this.mApiHandler = handler;
    }

    private IApiHandler getApiHandler() {
        if (mApiHandler == null) {
            throw new NullPointerException("ApiHandler must be not NULL!!!");
        }
        return mApiHandler;
    }

    /**
     * 添加一个基础路径
     *
     * @param key     开发者自定义关键字
     * @param baseUrl 基础路径
     */
    public void addBaseUrl(String key, String baseUrl) {
        mBaseUrlMap.put(key.hashCode(), baseUrl);
    }

    /**
     * 获取一个基础路径
     *
     * @param key 基础路径关键字
     * @return 基础路径
     */
    public String getBaseUrl(String key) {
        if (mBaseUrlMap.containsKey(key.hashCode())) {
            return mBaseUrlMap.get(key.hashCode());
        } else {
            return null;
        }
    }


    /**
     * 构建 ApiService
     *
     * @param baseUrl 项目基本路径
     * @param clz     APi接口类
     * @param client  okhttpclient
     * @param <T>     APi接口类
     * @return APi接口类实例
     */
    private <T> T buildApiService(String baseUrl, Class<T> clz, OkHttpClient client) {
        if (mRetrofit == null) {
            Retrofit.Builder builder = new Retrofit.Builder()
                    .baseUrl(baseUrl)
                    .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
                    .client(client);
            List<Converter.Factory> factories = getApiHandler().buildConverterFactory();
            for (Converter.Factory factory : factories) {
                builder.addConverterFactory(factory);
            }
            //这里可以添加其他配置
            mRetrofit = builder.build();
        }
        T service = mRetrofit.create(clz);
        //缓存生成的apiservice接口对象
        cacheApiServiceObj(clz.getName(), service);
        return service;
    }

    /**
     * 缓存Api请求接口服务
     *
     * @param key     关键字
     * @param service api接口请求服务实例
     * @param <T>     Api接口请求类型
     */
    private <T> void cacheApiServiceObj(String key, T service) {
        if (!mApiServiceMap.containsKey(key)) {
            mApiServiceMap.put(key, service);
        }
    }

    /**
     * 取消所有的网络请求
     */
    public void cancelAllRequest() {
        OkHttpClient client = getApiHandler().buildOkHttpClient();
        if (client != null) {
            client.dispatcher().cancelAll();
        }
    }

    /**
     * 根据api接口类获取特定Api请求接口服务对象
     *
     * @param clz apiservice接口类
     * @param <T> Api接口请求类型
     * @return 返回特定Api请求接口服务对象
     */
    @SuppressWarnings("all")
    public <T> T getApiService(Class<T> clz) {
        if (mApiServiceMap != null && mApiServiceMap.containsKey(clz.getName())) {
            return (T) mApiServiceMap.get(clz.getName());
        } else {
            return buildApiService(mDefBaseUrl, clz, getApiHandler().buildOkHttpClient());
        }
    }

    /**
     * 异步处理网络请求
     *
     * @param observable apiService定义的http请求
     * @param listener   响应回调
     * @param <T>        实现了{@link IResultData}接口的实体类才能触发异常重试机制
     */
    public <T extends IResultData> Disposable sendRequest(Observable<T> observable, OnHttpListener<T> listener) {
        return sendRequest("default", observable, listener);
    }

    /**
     * 异步处理网络请求
     *
     * @param tag        请求标签，异常重试会返回
     * @param observable apiService定义的http请求
     * @param listener   响应回调
     * @param <T>        实现了{@link IResultData}接口的实体类才能触发异常重试机制
     */
    public <T extends IResultData> Disposable sendRequest(String tag, Observable<T> observable, OnHttpListener<T> listener) {
        return wrapObservableWithRetryWhen(tag, Objects.requireNonNull(observable)).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(getNextConsumer(listener), getThrowableConsumer(listener));
    }

    /**
     * 包装同步处理网络请求
     *
     * @param observable apiService定义的http请求
     */
    public <T extends IResultData> Observable<T> wrapRequest(String tag, Observable<T> observable) {
        return wrapObservableWithRetryWhen(Objects.requireNonNull(tag), Objects.requireNonNull(observable));
    }

    /**
     * 变换原始的observable 使其支持在异常时可以重新执行
     *
     * @param observable 待转换的原始的observable
     * @param <T>        实现了{@link IResultData}接口的实体类才能触发异常重试机制
     * @return 转换后支持异常重试的observable
     */
    public <T extends IResultData> Observable<T> wrapObservableWithRetryWhen(String tag, Observable<T> observable) {
        return observable.flatMap(new Function<T, ObservableSource<T>>() {
            @Override
            public ObservableSource<T> apply(T t) throws Throwable {
                Exception exception = getApiHandler().retryWhenException(t.getCode());
                if (exception != null) {
                    return Observable.error(exception);
                } else {
                    return Observable.just(t);
                }
            }
        }).retryWhen(new RetryWhenFactor(tag));
    }


    /**
     * 异常重试的工厂类
     */
    private class RetryWhenFactor implements Function<Observable<? extends Throwable>, Observable<?>> {
        private int tryCount = 0;
        private String tag;

        RetryWhenFactor(String tag) {
            this.tag = tag;
        }

        @Override
        public Observable<?> apply(Observable<? extends Throwable> observable) throws Exception {
            return observable.flatMap(new Function<Throwable, ObservableSource<?>>() {
                @Override
                public ObservableSource<?> apply(Throwable throwable) throws Exception {
                    Observable retryWhenObservable = getApiHandler().retryWhenObservable(tag, ++tryCount, throwable);
                    if (retryWhenObservable == null) {
                        return Observable.error(throwable);
                    } else {
                        return retryWhenObservable;
                    }
                }
            });
        }
    }


    /**
     * rxjava 异常消费者分发请求异常
     */
    private Consumer<Throwable> getThrowableConsumer(final OnHttpListener listener) {
        return new Consumer<Throwable>() {
            @Override
            public void accept(Throwable throwable) throws Exception {
                getApiHandler().handleErrorException(throwable, listener);
            }
        };
    }

    /**
     * 处理请求成功的响应结果
     *
     * @param listener 请求回调
     */
    private <T extends IResultData> Consumer<T> getNextConsumer(final OnHttpListener<T> listener) {
        return new Consumer<T>() {
            @Override
            public void accept(T result) throws Throwable {
                if (getApiHandler().isResponseSuccess(result.getCode())) {//是否请求成功
                    listener.onSuccess(result);
                } else {
                    listener.onFailure(result.getCode(), result.getMessage());
                    getApiHandler().handleCustomizeException(result.getCode(), result.getMessage());
                }
            }
        };
    }


    /**
     * 构建文件上传表单(支持多文件和单文件)
     *
     * @param url      上传地址
     * @param params   参数列表
     * @param listener 回调监听
     * @return 返回构建的 request
     */
    private Request buildUploadFileRequest(String url, Map<String, Object> params, final OnHttpListener listener) {
        //配置参数
        MultipartBody.Builder multipartBodyBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM);
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            Object value = entry.getValue();
            String key = entry.getKey();
            if (value instanceof File[]) {//文件数组，多文件
                File[] files = (File[]) value;
                for (File file : files) {
                    RequestBody body = RequestBody.create(MEDIA_TYPE_STREAM, file);
                    multipartBodyBuilder.addFormDataPart(key, file.getName(), body);
                }
            } else if (value instanceof File) {//单文件
                RequestBody body = RequestBody.create(MEDIA_TYPE_STREAM, (File) value);
                multipartBodyBuilder.addFormDataPart(key, ((File) value).getName(), body);
            } else {//其他参数
                multipartBodyBuilder.addFormDataPart(key, value == null ? "" : value.toString());
            }
        }
        //更新进度
        ProgressRequestBody progressRequestBody = new ProgressRequestBody(multipartBodyBuilder.build(), new ProgressRequestBody.Listener() {
            @Override
            public void onRequestProgress(final long bytesWritten, final long contentLength, long networkSpeed) {
                listener.onUploadProgress(bytesWritten, contentLength, networkSpeed);
            }
        });
        //请求网络
        return new Request.Builder().url(url).post(progressRequestBody).build();
    }

    /**
     * 上传文件(支持进度条）
     *
     * @param url      上传地址
     * @param params   上传参数
     * @param listener 回调监听
     * @param isSync   true:同步；false:异步
     */
    private <T extends IResultData> Disposable uploadFiles(String url, Map<String, Object> params, OnHttpListener<T> listener, boolean isSync) {
        final Call call = getApiHandler().buildOkHttpClient().newCall(buildUploadFileRequest(url, params, listener));
        Observable<T> uploadObs = Observable.just(call).map(new Function<Call, Response>() {
            @Override
            public Response apply(Call call) throws Exception {
                return call.execute();
            }
        }).flatMap(new Function<Response, ObservableSource<T>>() {
            @Override
            public ObservableSource<T> apply(Response response) throws Throwable {
                if (response.isSuccessful()) {
                    T t = new Gson().fromJson(Objects.requireNonNull(response.body()).string(), new TypeToken<T>() {
                    }.getType());
                    return Observable.just(t);
                } else {
                    return Observable.error(new HttpException(retrofit2.Response.error(response.code(), Objects.requireNonNull(response.body()))));
                }
            }
        }).retryWhen(new RetryWhenFactor("upload"))
                .doFinally(new Action() {
                    @Override
                    public void run() throws Throwable {
                        call.cancel();
                    }
                });
        if (isSync) {
            return uploadObs.subscribe(getNextConsumer(listener), getThrowableConsumer(listener));
        } else {
            return uploadObs.subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(getNextConsumer(listener), getThrowableConsumer(listener));
        }
    }

    /**
     * 文件下载（支持进度条）
     *
     * @param url      下载路径
     * @param listener 监听接口
     * @param isSync   true 同步,false 异步
     */
    public Disposable downloadFile(final String url, final OnHttpListener<ResponseBody> listener, boolean isSync) {
        final Request request = new Request.Builder().url(url).build();
        final Call call = mApiHandler.buildOkHttpClient().newBuilder().addInterceptor(new Interceptor() {
            @NotNull
            @Override
            public Response intercept(@NotNull Chain chain) throws IOException {
                Response originalResponse = chain.proceed(chain.request());
                //包装响应体并返回
                return originalResponse.newBuilder().body(new ProgressResponseBody(originalResponse.body(), new ProgressResponseBody.ProgressResponseListener() {
                    @Override
                    public void onResponseProgress(long totalBytesRead, long totalLength, int progress) {
                        listener.onDownloadProgress(totalBytesRead, totalLength, progress);
                    }
                })).build();
            }
        }).build().newCall(request);

        Observable<ResponseBody> downLoadObs = Observable.just(call).map(new Function<Call, Response>() {
            @Override
            public Response apply(Call call) throws Exception {
                return call.execute();
            }
        }).flatMap(new Function<Response, ObservableSource<ResponseBody>>() {
            @Override
            public ObservableSource<ResponseBody> apply(Response response) throws Throwable {
                if (response.isSuccessful()) {
                    return Observable.just(Objects.requireNonNull(response.body()));
                } else {
                    return Observable.error(new HttpException(retrofit2.Response.error(response.code(), Objects.requireNonNull(response.body()))));
                }
            }
        }).retryWhen(new RetryWhenFactor("download"))
                .doFinally(new Action() {
                    @Override
                    public void run() throws Throwable {
                        call.cancel();
                    }
                });
        if (isSync) {
            return downLoadObs.subscribe(new Consumer<ResponseBody>() {
                @Override
                public void accept(ResponseBody responseBody) throws Throwable {
                    if (listener != null) {
                        listener.onSuccess(responseBody);
                    }
                }
            }, getThrowableConsumer(listener));
        } else {
            return downLoadObs.subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(new Consumer<ResponseBody>() {
                        @Override
                        public void accept(ResponseBody responseBody) throws Throwable {
                            if (listener != null) {
                                listener.onSuccess(responseBody);
                            }
                        }
                    }, getThrowableConsumer(listener));
        }
    }
}
